package it.unibo.refolding.pico.ui;

import it.unibo.refolding.pico.PicoDataAnalyser;
import it.unibo.refolding.pico.StripChartDirectionData;
import it.unibo.refolding.pico.ZLsbDeflectionLsbPoint;
import it.unimi.dsi.fastutil.shorts.ShortList;

import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.logging.Logger;

/**
 * TODO: Rollback in case saving the file fails.
 */
public class ContactPointAndSlopeZDefListener implements MouseListener, MouseMotionListener {

    private static final Logger logger = Logger.getLogger(ContactPointAndSlopeZDefListener.class
        .getSimpleName());
    
	private final ContactPointAndSlopeZDefPanel panel;
	private final double baselineFraction;
	private final boolean blockContactPointZ;

	/**
	 * @param blockContactPointZ When blocking the contact point z, the peak slopes maintain
	 * correctness.
	 */
	public ContactPointAndSlopeZDefListener(
		ContactPointAndSlopeZDefPanel panel, boolean blockContactPointZ,
		double baselineFraction) {
		this.panel = panel;
		this.blockContactPointZ = blockContactPointZ;
		this.baselineFraction = baselineFraction;
	}

	@Override
	public void mouseClicked(MouseEvent e) {
		int z = e.getX() + panel.minZ();
		double defOverDefScale = e.getY() / panel.defScale + panel.minDef();
    if (e.isPopupTrigger() || e.isControlDown()) {
			double diff = panel.h.getContactPoint().zLsb - z;
			if (diff != 0) {
        double oldSlope = panel.h.slope;
        panel.h.slope = (panel.h.getContactPoint().deflectionLsb - defOverDefScale) / diff;
        try {
          panel.h.save();
        } catch (RuntimeException ex) {
          logger.warning(ex.toString());
          panel.h.slope = oldSlope;
        }
        panel.repaint();
			}
		} else {
			if (blockContactPointZ) {
				panel.h.setContactPointDeflectionLsb(defOverDefScale);
			} else {
				panel.h.setContactPoint(new ZLsbDeflectionLsbPoint(z, defOverDefScale));
			}
			panel.repaint();
			if (!Double.isNaN(panel.h.slope)) {
				panel.h.save();
			}
		}
	}

	@Override
	public void mouseEntered(MouseEvent e) {
	}

	@Override
	public void mouseExited(MouseEvent e) {
	}

	@Override
	public void mousePressed(MouseEvent e) {
		int z = e.getX() + panel.minZ();
		int i;
		ShortList sharedZ = panel.h.sharedZ();
		// Even if the approach is much faster, it works fine.
		for (i = panel.h.size(); i > (panel.h.size() + 1) / 2 && sharedZ.getShort(i - 1) < z; --i) {
		}
		panel.dragFrom = panel.dragTo = i;
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		if (panel.dragFrom != panel.dragTo) {
			updateDragTo(e);
			double defLsb = findDefLsb();
			if (!Double.isNaN(defLsb)) {
			    panel.h.setContactPointDeflectionLsb(defLsb);
			}
			panel.dragFrom = panel.dragTo = panel.h.size();
			panel.repaint();
			panel.h.save();
		}
	}

  public double findDefLsb() {
    try {
      return PicoDataAnalyser.findEmpiricalRetractionContactPointDefLsbGivenZLsbAndSlope(
        new StripChartDirectionData(
          panel.h.sharedDef().subList(panel.dragFrom, panel.dragTo).toShortArray(),
          panel.h.sharedZ().subList(panel.dragFrom, panel.dragTo).toShortArray()),
        panel.h.retractionContactPointZLsb(), panel.h.slope, baselineFraction);
    } catch (IllegalArgumentException ex) {
      // Contact point 5738.418420580834 must not be lower than the end of the
      // retraction curve 5742.
      logger.warning(ex.toString());
      return Double.NaN;
    }
  }

	void updateDragTo(MouseEvent e) {
		int z = e.getX() + panel.minZ();
		int i;
		ShortList sharedZ = panel.h.sharedZ();
		for (i = panel.dragFrom; i < sharedZ.size() && sharedZ.getShort(i) >= z; ++i) {
		}
		panel.dragTo = i;
	}

	@Override
	public void mouseDragged(MouseEvent e) {
		updateDragTo(e);
		panel.repaint();
	}

	@Override
	public void mouseMoved(MouseEvent e) {
	}
}
